{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "8b152170",
   "metadata": {},
   "source": [
    "### 代码实现"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "d27ca2df",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 导入必要的库\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "from torchinfo import summary"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1750a905",
   "metadata": {},
   "source": [
    "### Dense Layer"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "852700ad",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 定义dense block中的dense layer\n",
    "class _DenseLayer(nn.Module):\n",
    "    # 构造函数，接收输入通道数num_input_features，输出通道数growth_rate，卷积层的缩放比例bn_size\n",
    "    def __init__(self, num_input_features, growth_rate, bn_size):\n",
    "        super().__init__()\n",
    "        # 定义第一个卷积层，包括BN层、ReLU激活函数和1x1卷积层\n",
    "        self.conv1 = nn.Sequential(\n",
    "            nn.BatchNorm2d(num_input_features),\n",
    "            nn.ReLU(inplace=True),\n",
    "            nn.Conv2d(num_input_features, bn_size * growth_rate, kernel_size=1, stride=1, bias=False)\n",
    "        )\n",
    "        # 定义第二个卷积层，包括BN层、ReLU激活函数和3x3卷积层\n",
    "        self.conv2 = nn.Sequential(\n",
    "            nn.BatchNorm2d(bn_size * growth_rate),\n",
    "            nn.ReLU(inplace=True),\n",
    "            nn.Conv2d(bn_size * growth_rate, growth_rate, kernel_size=3, stride=1, padding=1, bias=False)\n",
    "        )\n",
    "\n",
    "    # 定义前向传播函数\n",
    "    def forward(self, x):\n",
    "        # BN+ReLU+1x1卷积\n",
    "        out = self.conv1(x)\n",
    "        # BN+ReLU+3x3卷积\n",
    "        out = self.conv2(out)\n",
    "        # 将输入和输出进行拼接后返回结果\n",
    "        return torch.cat([x, out], 1)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dd49d372",
   "metadata": {},
   "source": [
    "### Dense Block"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "c7089c31",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 定义dense block\n",
    "class _DenseBlock(nn.Module):\n",
    "    # 构造函数，包含密集连接层的数量num_layers，输入通道数num_input_features，输出通道数growth_rate，卷积层的缩放比例bn_size\n",
    "    def __init__(self, num_layers, num_input_features, growth_rate, bn_size):\n",
    "        super().__init__()\n",
    "        \n",
    "        # 保存密集连接层的列表\n",
    "        layers = []\n",
    "        # 构建num_layers个密集连接层\n",
    "        for i in range(num_layers):\n",
    "            # 构建一个密集连接层，其中输入通道数为num_input_features + i * growth_rate逐层递增\n",
    "            layer = _DenseLayer(num_input_features + i * growth_rate, growth_rate, bn_size)\n",
    "            # 将构建好的密集连接层添加到列表中保存\n",
    "            layers.append(layer)\n",
    "        # 将所有密集连接层封装到Sequential中保存为block\n",
    "        self.block = nn.Sequential(*layers)\n",
    "        \n",
    "    # 定义前向传播函数\n",
    "    def forward(self, x):\n",
    "        # 经过当前block输出即可\n",
    "        return self.block(x)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a11091c9",
   "metadata": {},
   "source": [
    "### Transition Layer"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "789ca9b8",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 定义dense block之间的transition layer\n",
    "class _Transition(nn.Module):\n",
    "    # 构造函数，输入通道数num_input_features，输出通道数num_output_features\n",
    "    def __init__(self, num_input_features, num_output_features):\n",
    "        super().__init__()\n",
    "        # 定义一个转换层，用于降维和调整特征图的size，包含BN+ReLU+1x1卷积+平均池化层\n",
    "        self.trans = nn.Sequential(\n",
    "            nn.BatchNorm2d(num_input_features),\n",
    "            nn.ReLU(inplace=True),\n",
    "            nn.Conv2d(num_input_features, num_output_features, kernel_size=1, stride=1, bias=False),\n",
    "            nn.AvgPool2d(kernel_size=2, stride=2)\n",
    "        )\n",
    "    \n",
    "    # 定义前向传播函数\n",
    "    def forward(self, x):\n",
    "        # 经过转换层后输出即可\n",
    "        return self.trans(x)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "300fabed",
   "metadata": {},
   "source": [
    "### 结构定义"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "4b0b4118",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 定义DenseNet的网络结构\n",
    "class DenseNet(nn.Module):\n",
    "    # 构造函数，包含dense block的数量block_config，输入通道数num_input_features，输出通道数growth_rate，\n",
    "    #           卷积层的缩放比例bn_size和类别数num_classes\n",
    "    def __init__(self, block_config, num_init_features=64, growth_rate=32, bn_size=4, num_classes=1000):\n",
    "\n",
    "        super().__init__()\n",
    "\n",
    "        # 第一部分，7x7卷积+BN+ReLU+最大池化层\n",
    "        self.features = nn.Sequential(\n",
    "            nn.Conv2d(3, num_init_features, kernel_size=7, stride=2, padding=3, bias=False),\n",
    "            nn.BatchNorm2d(num_init_features),\n",
    "            nn.ReLU(inplace=True),\n",
    "            nn.MaxPool2d(kernel_size=3, stride=2, padding=1)\n",
    "        )\n",
    "\n",
    "        # 下面依次定义dense block和transition layer，对应第二部分和第三部分\n",
    "        num_features = num_init_features # 记录通道数\n",
    "        layers = [] # 网络结构保存列表\n",
    "        # 遍历每层dense block的数量列表\n",
    "        for i, num_layers in enumerate(block_config):\n",
    "            # 创建dense block，其中包含num_layers个dense layer\n",
    "            block = _DenseBlock(num_layers=num_layers, num_input_features=num_features, \n",
    "                                growth_rate=growth_rate, bn_size=bn_size)\n",
    "            layers.append(block)\n",
    "            num_features = num_features + num_layers * growth_rate # 更新特征图维度\n",
    "            # 如果不是最后一个dense block，则添加一个transition layer，特征图维度除以2\n",
    "            if i != len(block_config) - 1:\n",
    "                trans = _Transition(num_input_features=num_features, num_output_features=num_features // 2)\n",
    "                layers.append(trans)\n",
    "                num_features = num_features // 2\n",
    "        # 添加一个BN层\n",
    "        layers.append(nn.BatchNorm2d(num_features))\n",
    "        # 调用nn.Sequential完成第二部分和第三部分\n",
    "        self.denseblock = nn.Sequential(*layers)\n",
    "\n",
    "        # 第四部分，全连接层\n",
    "        self.classifier = nn.Linear(num_features, num_classes)\n",
    "\n",
    "    # 定义前向传播函数\n",
    "    def forward(self, x):\n",
    "        # 第一部分\n",
    "        features = self.features(x)\n",
    "        # 第二、三部分\n",
    "        features = self.denseblock(features)\n",
    "        # ReLU\n",
    "        out = F.relu(features, inplace=True)\n",
    "        # 第四部分，平均池化+全连接层\n",
    "        out = F.avg_pool2d(out, kernel_size=7, stride=1).view(features.size(0), -1)\n",
    "        out = self.classifier(out)\n",
    "        # 输出\n",
    "        return out"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "50614d99",
   "metadata": {},
   "source": [
    "### 封装函数"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "45faa4eb",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 封装函数对应4个模型，num_classes表示类别数\n",
    "# 其中数值与论文中的数值一致\n",
    "def densenet121(num_classes=1000):\n",
    "    return DenseNet(block_config=(6, 12, 24, 16), num_init_features=64, \n",
    "                    growth_rate=32, num_classes=num_classes)\n",
    "\n",
    "def densenet161(num_classes=1000):\n",
    "    return DenseNet(block_config=(6, 12, 36, 24), num_init_features=96, \n",
    "                    growth_rate=48, num_classes=num_classes)\n",
    "\n",
    "def densenet169(num_classes=1000):\n",
    "    return DenseNet(block_config=(6, 12, 32, 32), num_init_features=64, \n",
    "                    growth_rate=32, num_classes=num_classes)\n",
    "\n",
    "def densenet201(num_classes=1000):\n",
    "    return DenseNet(block_config=(6, 12, 48, 32), num_init_features=64, \n",
    "                    growth_rate=32, num_classes=num_classes)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2bf3ebf0",
   "metadata": {},
   "source": [
    "### 网络结构"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "68d87977",
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "====================================================================================================\n",
       "Layer (type:depth-idx)                             Output Shape              Param #\n",
       "====================================================================================================\n",
       "DenseNet                                           [1, 1000]                 --\n",
       "├─Sequential: 1-1                                  [1, 64, 56, 56]           --\n",
       "│    └─Conv2d: 2-1                                 [1, 64, 112, 112]         9,408\n",
       "│    └─BatchNorm2d: 2-2                            [1, 64, 112, 112]         128\n",
       "│    └─ReLU: 2-3                                   [1, 64, 112, 112]         --\n",
       "│    └─MaxPool2d: 2-4                              [1, 64, 56, 56]           --\n",
       "├─Sequential: 1-2                                  [1, 1024, 7, 7]           --\n",
       "│    └─_DenseBlock: 2-5                            [1, 256, 56, 56]          --\n",
       "│    │    └─Sequential: 3-1                        [1, 256, 56, 56]          335,040\n",
       "│    └─_Transition: 2-6                            [1, 128, 28, 28]          --\n",
       "│    │    └─Sequential: 3-2                        [1, 128, 28, 28]          33,280\n",
       "│    └─_DenseBlock: 2-7                            [1, 512, 28, 28]          --\n",
       "│    │    └─Sequential: 3-3                        [1, 512, 28, 28]          919,680\n",
       "│    └─_Transition: 2-8                            [1, 256, 14, 14]          --\n",
       "│    │    └─Sequential: 3-4                        [1, 256, 14, 14]          132,096\n",
       "│    └─_DenseBlock: 2-9                            [1, 1024, 14, 14]         --\n",
       "│    │    └─Sequential: 3-5                        [1, 1024, 14, 14]         2,837,760\n",
       "│    └─_Transition: 2-10                           [1, 512, 7, 7]            --\n",
       "│    │    └─Sequential: 3-6                        [1, 512, 7, 7]            526,336\n",
       "│    └─_DenseBlock: 2-11                           [1, 1024, 7, 7]           --\n",
       "│    │    └─Sequential: 3-7                        [1, 1024, 7, 7]           2,158,080\n",
       "│    └─BatchNorm2d: 2-12                           [1, 1024, 7, 7]           2,048\n",
       "├─Linear: 1-3                                      [1, 1000]                 1,025,000\n",
       "====================================================================================================\n",
       "Total params: 7,978,856\n",
       "Trainable params: 7,978,856\n",
       "Non-trainable params: 0\n",
       "Total mult-adds (G): 2.83\n",
       "====================================================================================================\n",
       "Input size (MB): 0.60\n",
       "Forward/backward pass size (MB): 180.54\n",
       "Params size (MB): 31.92\n",
       "Estimated Total Size (MB): 213.06\n",
       "===================================================================================================="
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 查看模型结构及参数量，input_size表示示例输入数据的维度信息\n",
    "summary(densenet121(), input_size=(1, 3, 224, 224))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4df6fa8a",
   "metadata": {},
   "source": [
    "### torchvision"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "4ee0c6f8",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "==========================================================================================\n",
       "Layer (type:depth-idx)                   Output Shape              Param #\n",
       "==========================================================================================\n",
       "DenseNet                                 [1, 1000]                 --\n",
       "├─Sequential: 1-1                        [1, 1024, 7, 7]           --\n",
       "│    └─Conv2d: 2-1                       [1, 64, 112, 112]         9,408\n",
       "│    └─BatchNorm2d: 2-2                  [1, 64, 112, 112]         128\n",
       "│    └─ReLU: 2-3                         [1, 64, 112, 112]         --\n",
       "│    └─MaxPool2d: 2-4                    [1, 64, 56, 56]           --\n",
       "│    └─_DenseBlock: 2-5                  [1, 256, 56, 56]          --\n",
       "│    │    └─_DenseLayer: 3-1             [1, 32, 56, 56]           45,440\n",
       "│    │    └─_DenseLayer: 3-2             [1, 32, 56, 56]           49,600\n",
       "│    │    └─_DenseLayer: 3-3             [1, 32, 56, 56]           53,760\n",
       "│    │    └─_DenseLayer: 3-4             [1, 32, 56, 56]           57,920\n",
       "│    │    └─_DenseLayer: 3-5             [1, 32, 56, 56]           62,080\n",
       "│    │    └─_DenseLayer: 3-6             [1, 32, 56, 56]           66,240\n",
       "│    └─_Transition: 2-6                  [1, 128, 28, 28]          --\n",
       "│    │    └─BatchNorm2d: 3-7             [1, 256, 56, 56]          512\n",
       "│    │    └─ReLU: 3-8                    [1, 256, 56, 56]          --\n",
       "│    │    └─Conv2d: 3-9                  [1, 128, 56, 56]          32,768\n",
       "│    │    └─AvgPool2d: 3-10              [1, 128, 28, 28]          --\n",
       "│    └─_DenseBlock: 2-7                  [1, 512, 28, 28]          --\n",
       "│    │    └─_DenseLayer: 3-11            [1, 32, 28, 28]           53,760\n",
       "│    │    └─_DenseLayer: 3-12            [1, 32, 28, 28]           57,920\n",
       "│    │    └─_DenseLayer: 3-13            [1, 32, 28, 28]           62,080\n",
       "│    │    └─_DenseLayer: 3-14            [1, 32, 28, 28]           66,240\n",
       "│    │    └─_DenseLayer: 3-15            [1, 32, 28, 28]           70,400\n",
       "│    │    └─_DenseLayer: 3-16            [1, 32, 28, 28]           74,560\n",
       "│    │    └─_DenseLayer: 3-17            [1, 32, 28, 28]           78,720\n",
       "│    │    └─_DenseLayer: 3-18            [1, 32, 28, 28]           82,880\n",
       "│    │    └─_DenseLayer: 3-19            [1, 32, 28, 28]           87,040\n",
       "│    │    └─_DenseLayer: 3-20            [1, 32, 28, 28]           91,200\n",
       "│    │    └─_DenseLayer: 3-21            [1, 32, 28, 28]           95,360\n",
       "│    │    └─_DenseLayer: 3-22            [1, 32, 28, 28]           99,520\n",
       "│    └─_Transition: 2-8                  [1, 256, 14, 14]          --\n",
       "│    │    └─BatchNorm2d: 3-23            [1, 512, 28, 28]          1,024\n",
       "│    │    └─ReLU: 3-24                   [1, 512, 28, 28]          --\n",
       "│    │    └─Conv2d: 3-25                 [1, 256, 28, 28]          131,072\n",
       "│    │    └─AvgPool2d: 3-26              [1, 256, 14, 14]          --\n",
       "│    └─_DenseBlock: 2-9                  [1, 1024, 14, 14]         --\n",
       "│    │    └─_DenseLayer: 3-27            [1, 32, 14, 14]           70,400\n",
       "│    │    └─_DenseLayer: 3-28            [1, 32, 14, 14]           74,560\n",
       "│    │    └─_DenseLayer: 3-29            [1, 32, 14, 14]           78,720\n",
       "│    │    └─_DenseLayer: 3-30            [1, 32, 14, 14]           82,880\n",
       "│    │    └─_DenseLayer: 3-31            [1, 32, 14, 14]           87,040\n",
       "│    │    └─_DenseLayer: 3-32            [1, 32, 14, 14]           91,200\n",
       "│    │    └─_DenseLayer: 3-33            [1, 32, 14, 14]           95,360\n",
       "│    │    └─_DenseLayer: 3-34            [1, 32, 14, 14]           99,520\n",
       "│    │    └─_DenseLayer: 3-35            [1, 32, 14, 14]           103,680\n",
       "│    │    └─_DenseLayer: 3-36            [1, 32, 14, 14]           107,840\n",
       "│    │    └─_DenseLayer: 3-37            [1, 32, 14, 14]           112,000\n",
       "│    │    └─_DenseLayer: 3-38            [1, 32, 14, 14]           116,160\n",
       "│    │    └─_DenseLayer: 3-39            [1, 32, 14, 14]           120,320\n",
       "│    │    └─_DenseLayer: 3-40            [1, 32, 14, 14]           124,480\n",
       "│    │    └─_DenseLayer: 3-41            [1, 32, 14, 14]           128,640\n",
       "│    │    └─_DenseLayer: 3-42            [1, 32, 14, 14]           132,800\n",
       "│    │    └─_DenseLayer: 3-43            [1, 32, 14, 14]           136,960\n",
       "│    │    └─_DenseLayer: 3-44            [1, 32, 14, 14]           141,120\n",
       "│    │    └─_DenseLayer: 3-45            [1, 32, 14, 14]           145,280\n",
       "│    │    └─_DenseLayer: 3-46            [1, 32, 14, 14]           149,440\n",
       "│    │    └─_DenseLayer: 3-47            [1, 32, 14, 14]           153,600\n",
       "│    │    └─_DenseLayer: 3-48            [1, 32, 14, 14]           157,760\n",
       "│    │    └─_DenseLayer: 3-49            [1, 32, 14, 14]           161,920\n",
       "│    │    └─_DenseLayer: 3-50            [1, 32, 14, 14]           166,080\n",
       "│    └─_Transition: 2-10                 [1, 512, 7, 7]            --\n",
       "│    │    └─BatchNorm2d: 3-51            [1, 1024, 14, 14]         2,048\n",
       "│    │    └─ReLU: 3-52                   [1, 1024, 14, 14]         --\n",
       "│    │    └─Conv2d: 3-53                 [1, 512, 14, 14]          524,288\n",
       "│    │    └─AvgPool2d: 3-54              [1, 512, 7, 7]            --\n",
       "│    └─_DenseBlock: 2-11                 [1, 1024, 7, 7]           --\n",
       "│    │    └─_DenseLayer: 3-55            [1, 32, 7, 7]             103,680\n",
       "│    │    └─_DenseLayer: 3-56            [1, 32, 7, 7]             107,840\n",
       "│    │    └─_DenseLayer: 3-57            [1, 32, 7, 7]             112,000\n",
       "│    │    └─_DenseLayer: 3-58            [1, 32, 7, 7]             116,160\n",
       "│    │    └─_DenseLayer: 3-59            [1, 32, 7, 7]             120,320\n",
       "│    │    └─_DenseLayer: 3-60            [1, 32, 7, 7]             124,480\n",
       "│    │    └─_DenseLayer: 3-61            [1, 32, 7, 7]             128,640\n",
       "│    │    └─_DenseLayer: 3-62            [1, 32, 7, 7]             132,800\n",
       "│    │    └─_DenseLayer: 3-63            [1, 32, 7, 7]             136,960\n",
       "│    │    └─_DenseLayer: 3-64            [1, 32, 7, 7]             141,120\n",
       "│    │    └─_DenseLayer: 3-65            [1, 32, 7, 7]             145,280\n",
       "│    │    └─_DenseLayer: 3-66            [1, 32, 7, 7]             149,440\n",
       "│    │    └─_DenseLayer: 3-67            [1, 32, 7, 7]             153,600\n",
       "│    │    └─_DenseLayer: 3-68            [1, 32, 7, 7]             157,760\n",
       "│    │    └─_DenseLayer: 3-69            [1, 32, 7, 7]             161,920\n",
       "│    │    └─_DenseLayer: 3-70            [1, 32, 7, 7]             166,080\n",
       "│    └─BatchNorm2d: 2-12                 [1, 1024, 7, 7]           2,048\n",
       "├─Linear: 1-2                            [1, 1000]                 1,025,000\n",
       "==========================================================================================\n",
       "Total params: 7,978,856\n",
       "Trainable params: 7,978,856\n",
       "Non-trainable params: 0\n",
       "Total mult-adds (G): 2.83\n",
       "==========================================================================================\n",
       "Input size (MB): 0.60\n",
       "Forward/backward pass size (MB): 180.54\n",
       "Params size (MB): 31.92\n",
       "Estimated Total Size (MB): 213.06\n",
       "=========================================================================================="
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 查看torchvision自带的模型结构及参数量\n",
    "from torchvision import models\n",
    "summary(models.densenet121(), input_size=(1, 3, 224, 224))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f8429db2",
   "metadata": {},
   "source": [
    "### 模型训练"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "9c3fb22b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch: 0 Loss: 2.6246336445827274 Acc: 0.03529411764705882\n",
      "Epoch: 10 Loss: 2.4508019464199036 Acc: 0.17352941176470588\n",
      "Epoch: 20 Loss: 2.3702510442054217 Acc: 0.257843137254902\n",
      "Epoch: 30 Loss: 2.310527433003603 Acc: 0.3392156862745098\n",
      "Epoch: 40 Loss: 2.259450921415037 Acc: 0.37745098039215685\n",
      "Epoch: 50 Loss: 2.2196614920857183 Acc: 0.41862745098039217\n",
      "Epoch: 60 Loss: 2.170892211891438 Acc: 0.45784313725490194\n",
      "Epoch: 70 Loss: 2.1314921703382517 Acc: 0.492156862745098\n",
      "Epoch: 80 Loss: 2.0921777536321633 Acc: 0.5147058823529411\n",
      "Epoch: 90 Loss: 2.0567074716229428 Acc: 0.5196078431372549\n",
      "Epoch: 100 Loss: 2.0274026593455523 Acc: 0.5235294117647059\n",
      "Epoch: 110 Loss: 1.9874356147854575 Acc: 0.5166666666666667\n",
      "Epoch: 120 Loss: 1.9602205390341274 Acc: 0.5509803921568628\n",
      "Epoch: 130 Loss: 1.9483637491000336 Acc: 0.5754901960784313\n",
      "Epoch: 140 Loss: 1.9112998645671402 Acc: 0.5980392156862745\n",
      "Epoch: 150 Loss: 1.8827491679198882 Acc: 0.5705882352941176\n",
      "Epoch: 160 Loss: 1.8423764428096985 Acc: 0.6186274509803922\n",
      "Epoch: 170 Loss: 1.8327524220608502 Acc: 0.596078431372549\n",
      "Epoch: 180 Loss: 1.7907927285374234 Acc: 0.6264705882352941\n",
      "Epoch: 190 Loss: 1.773257426019121 Acc: 0.6323529411764706\n",
      "100%|██████████| 200/200 [2:18:16<00:00, 41.48s/it]\n"
     ]
    },
    {
     "data": {
      "image/png": "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yNKlSxk0aBBRUVE4HA5mzZp11uMXL16Mw+Eo9dq+ffv5lllEREQuIx4VPSE7O5u2bdty7733MnTo0HKft2PHDgIDA4vfh4WFVfSrRURE5DJU4TAycOBABg4cWOEvCg8PJzg4uMLniYiIyOXtkvUZadeuHZGRkfTr149Fixad9dj8/HwyMzNLvEREROTydNHDSGRkJO+++y7Tp09nxowZNG3alH79+rF06dIznjNx4kSCgoKKXzExMRe7mCIiImITh2VZ1nmf7HAwc+ZMhgwZUqHzBg0ahMPhYPbs2WXuz8/PJz8/v/h9ZmYmMTExZGRklOh3IiIiIpVXZmYmQUFB53x+2zK0t0uXLuzateuM+729vQkMDCzxEhERkcuTLWFk/fr1REZG2vHVIiIiUslUeDTN8ePH2b17d/H7+Ph4NmzYQEhICHXr1mXChAkcPHiQjz/+GIBJkyZRv359WrZsidPpZOrUqUyfPp3p06dfuKsQERGRKqvCYWTNmjX06dOn+P3YsWMBuOeee/jwww9JSkoiMTGxeL/T6WTcuHEcPHgQX19fWrZsyZw5c7j22msvQPFFRESkqvtdHVgvlfJ2gBEREZHKo1J3YBURERE5SWFEREREbKUwIiIiIrZSGBERERFbKYyIiIiIrRRGRERExFYKIyIiImIrhRERERGxlcKIiIiI2EphRERERGylMCIiIiK2UhgRERERWymMiIiIiK0URkRERMRWCiMiIiJiK4URERERsZXCiIiIiNhKYURERERspTAiIiIitlIYEREREVspjIiIiIitFEZERETEVgojIiIiYiuFEREREbGVwoiIiIjYSmFEREREbKUwIiIiIrZSGBERERFbKYyIiIiIrRRGRERExFYKIyIiImIrhRERERGxlcKIiIiI2EphRERERGylMCIiIiK2UhgRERERWymMiIiIiK0URkRERMRWCiMiIiJiK4URERERsZXCiIiIiNhKYURERERspTAiIiIitlIYEREREVspjIiIiIitFEZERETEVgojIiIiYiuFEREREbGVwoiIiIjYSmFEREREbKUwIiIiIrZSGBERERFbKYyIiIiIrRRGRERExFYVDiNLly5l0KBBREVF4XA4mDVr1jnPWbJkCR06dMDHx4cGDRrw9ttvn09ZRURE5DJU4TCSnZ1N27Ztef3118t1fHx8PNdeey09evRg/fr1/PWvf+XRRx9l+vTpFS6siIiIXH48KnrCwIEDGThwYLmPf/vtt6lbty6TJk0CoHnz5qxZs4aXX36ZoUOHVvTrRURE5DJz0fuMrFy5kv79+5fYNmDAANasWUNBQUGZ5+Tn55OZmVniJSIiIpenix5GkpOTqV27dolttWvXprCwkNTU1DLPmThxIkFBQcWvmJiYi11MERERscklGU3jcDhKvLcsq8ztJ02YMIGMjIzi1/79+y96GUVERMQeFe4zUlEREREkJyeX2JaSkoKHhwe1atUq8xxvb2+8vb0vdtFERESkErjoNSNdu3Zl/vz5JbbNmzePjh074unpebG/XkRERCq5CoeR48ePs2HDBjZs2ACYobsbNmwgMTERME0sw4cPLz5+5MiRJCQkMHbsWOLi4pg8eTIffPAB48aNuzBXICIiIlVahZtp1qxZQ58+fYrfjx07FoB77rmHDz/8kKSkpOJgAhAbG8vcuXN5/PHHeeONN4iKiuLVV1/VsF4REREBwGGd7E1aiWVmZhIUFERGRgaBgYF2F0dERETKobzPb61NIyIiIrZSGBERERFbKYyIiIiIrRRGRERExFYKIyIiImIrhRERERGxlcKIiIiI2EphRERERGylMCIiIiK2UhgRERERWymMiIiIiK0URkRERMRWCiMiIiJiK4URERERsZXCiIiIiNhKYURERERspTAiIiIitlIYEREREVspjIiIiIitFEZERETEVgojIiIiYiuFEREREbGVwoiIiIjYSmFEREREbKUwIiIiIrZSGBERERFbKYyIiIiIrRRGRERExFYKIyIiImIrhRERERGxlcKIiIiI2EphRERERGylMCIiIiK2UhgRERERWymMiIiIiK0URkRERMRWCiMiIiJiK4URERGRy42ryLyqCA+7CyAiIlKlFOSBMxv8a13c73G5IDsFjiXAke2QeRAaD4DoDmUfb1mwaz5s+Rq2z4XAKBjxHdQIP/d3FRWa/7rbEwsURkREpPqwLHA4Sm4ryIPv/wy+NaHnk+Bd4+znfzIEDq6FYZ9B4z+U/7tdLvPdv/3+38rLgG9Gw4654CosuW/pS9DtEej9V/D0ObX9yA74biwkLD+1LXUHfHoLjJgDWLD/V/N5lguyj0DGQTi6B1K2Q+pOuOtriO1Z/uu5gBRGRETk/GUlmwd0YOSl+849C2HX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cWM6duzIunXraN++/aUuaoWd79/LqngfJ0+ezJ133omPj0+J7VXlHp7p+QD2/y5Wy2aa0NBQ3N3dSyW6lJSUUumwKnnkkUeYPXs2ixYtIjo6+qzHRkZGUq9ePXbt2nWJSndh+fv707p1a3bt2lU8quZyuZ8JCQksWLCA+++//6zHVeV7WJ57FhERgdPp5NixY2c8piooKCjg1ltvJT4+nvnz559zWfb27dvj6elZJe8rlP57ebncx2XLlrFjx45z/l5C5byHZ3o+VJbfxWoZRry8vOjQoUOpKrT58+fTrVs3m0p1/izLYvTo0cyYMYOFCxcSGxt7znPS0tLYv38/kZGRl6CEF15+fj5xcXFERkYWV4+efj+dTidLliypkvdzypQphIeHc9111531uKp8D8tzzzp06ICnp2eJY5KSktiyZUuVua8ng8iuXbtYsGABtWrVOuc5W7dupaCgoEreVyj99/JyuI9gais7dOhA27Ztz3lsZbqH53o+VJrfxQvSDbYKmjZtmuXp6Wl98MEH1rZt26wxY8ZY/v7+1r59++wuWoU9/PDDVlBQkLV48WIrKSmp+JWTk2NZlmVlZWVZTzzxhLVixQorPj7eWrRokdW1a1erTp06VmZmps2lL58nnnjCWrx4sbV3715r1apV1vXXX28FBAQU36/nn3/eCgoKsmbMmGFt3rzZuv32263IyMgqc30nFRUVWXXr1rXGjx9fYntVvIdZWVnW+vXrrfXr11uA9d///tdav3598UiS8tyzkSNHWtHR0daCBQusdevWWX379rXatm1rFRYW2nVZJZztGgsKCqwbbrjBio6OtjZs2FDidzM/P9+yLMvavXu39dxzz1mrV6+24uPjrTlz5ljNmjWz2rVrVyWusbx/LyvzfTzX31PLsqyMjAzLz8/Peuutt0qdX9nv4bmeD5ZVOX4Xq20YsSzLeuONN6x69epZXl5eVvv27UsMha1KgDJfU6ZMsSzLsnJycqz+/ftbYWFhlqenp1W3bl3rnnvusRITE+0teAXcdtttVmRkpOXp6WlFRUVZN910k7V169bi/S6Xy3r22WetiIgIy9vb2+rZs6e1efNmG0t8fn788UcLsHbs2FFie1W8h4sWLSrz7+U999xjWVb57llubq41evRoKyQkxPL19bWuv/76SnXNZ7vG+Pj4M/5uLlq0yLIsy0pMTLR69uxphYSEWF5eXlbDhg2tRx991EpLS7P3wk5ztmss79/Lynwfz/X31LIs65133rF8fX2t9PT0UudX9nt4rueDZVWO30XHicKKiIiI2KJa9hkRERGRykNhRERERGylMCIiIiK2UhgRERERWymMiIiIiK0URkRERMRWCiMiIiJiK4URERERsZXCiIiIiNhKYURERERspTAiIiIitlIYEREREVv9Px5MJDpv0wPvAAAAAElFTkSuQmCC\n",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Accuracy: 0.6392156862745098\n"
     ]
    }
   ],
   "source": [
    "# 导入必要的库\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.optim as optim\n",
    "from torch.utils.data import DataLoader\n",
    "from torchvision import datasets, transforms, models\n",
    "from tqdm import *\n",
    "import numpy as np\n",
    "import sys\n",
    "\n",
    "# 设备检测，若未检测到cuda设备则在CPU上运行\n",
    "device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n",
    "\n",
    "# 设置随机种子\n",
    "torch.manual_seed(0)\n",
    "\n",
    "# 定义模型、优化器、损失函数\n",
    "model = densenet121(num_classes=102).to(device)\n",
    "optimizer = optim.SGD(model.parameters(), lr=0.002, momentum=0.9)\n",
    "criterion = nn.CrossEntropyLoss()\n",
    "\n",
    "# 设置训练集的数据变换，进行数据增强\n",
    "trainform_train = transforms.Compose([\n",
    "    transforms.RandomRotation(30), # 随机旋转 -30度到30度之间\n",
    "    transforms.RandomResizedCrop((224, 224)), # 随机比例裁剪并进行resize\n",
    "    transforms.RandomHorizontalFlip(p = 0.5), # 随机水平翻转\n",
    "    transforms.RandomVerticalFlip(p = 0.5), # 随机垂直翻转\n",
    "    transforms.ToTensor(),  # 将数据转换为张量\n",
    "    # 对三通道数据进行归一化(均值，标准差)，数值是从ImageNet数据集上的百万张图片中随机抽样计算得到\n",
    "    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n",
    "])\n",
    "\n",
    "# 设置测试集的数据变换，不进行数据增强，仅使用resize和归一化\n",
    "transform_test = transforms.Compose([\n",
    "    transforms.Resize((224, 224)),  # resize\n",
    "    transforms.ToTensor(),  # 将数据转换为张量\n",
    "    # 对三通道数据进行归一化(均值，标准差)，数值是从ImageNet数据集上的百万张图片中随机抽样计算得到\n",
    "    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n",
    "])\n",
    "\n",
    "# 加载训练数据，需要特别注意的是Flowers102数据集，test簇的数据量较多些，所以这里使用\"test\"作为训练集\n",
    "train_dataset = datasets.Flowers102(root='../data/flowers102', split=\"test\", download=True, transform=trainform_train)\n",
    "# 实例化训练数据加载器\n",
    "train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True, num_workers=4)\n",
    "# 加载测试数据，使用\"train\"作为测试集\n",
    "test_dataset = datasets.Flowers102(root='../data/flowers102', split=\"train\", download=True, transform=transform_test)\n",
    "# 实例化测试数据加载器\n",
    "test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False, num_workers=4)\n",
    "\n",
    "# 设置epoch数并开始训练\n",
    "num_epochs = 200  # 设置epoch数\n",
    "loss_history = []  # 创建损失历史记录列表\n",
    "acc_history = []   # 创建准确率历史记录列表\n",
    "\n",
    "# tqdm用于显示进度条并评估任务时间开销\n",
    "for epoch in tqdm(range(num_epochs), file=sys.stdout):\n",
    "    # 记录损失和预测正确数\n",
    "    total_loss = 0\n",
    "    total_correct = 0\n",
    "    \n",
    "    # 批量训练\n",
    "    model.train()\n",
    "    for inputs, labels in train_loader:\n",
    "        # 将数据转移到指定计算资源设备上\n",
    "        inputs = inputs.to(device)\n",
    "        labels = labels.to(device)\n",
    "        \n",
    "        # 预测、损失函数、反向传播\n",
    "        optimizer.zero_grad()\n",
    "        outputs = model(inputs)\n",
    "        loss = criterion(outputs, labels)\n",
    "        loss.backward()\n",
    "        optimizer.step()\n",
    "        \n",
    "        # 记录训练集loss\n",
    "        total_loss += loss.item()\n",
    "    \n",
    "    # 测试模型，不计算梯度\n",
    "    model.eval()\n",
    "    with torch.no_grad():\n",
    "        for inputs, labels in test_loader:\n",
    "            # 将数据转移到指定计算资源设备上\n",
    "            inputs = inputs.to(device)\n",
    "            labels = labels.to(device)\n",
    "            \n",
    "            # 预测\n",
    "            outputs = model(inputs)\n",
    "            # 记录测试集预测正确数\n",
    "            total_correct += (outputs.argmax(1) == labels).sum().item()\n",
    "        \n",
    "    # 记录训练集损失和测试集准确率\n",
    "    loss_history.append(np.log10(total_loss))  # 将损失加入损失历史记录列表，由于数值有时较大，这里取对数\n",
    "    acc_history.append(total_correct / len(test_dataset))# 将准确率加入准确率历史记录列表\n",
    "    \n",
    "    # 打印中间值\n",
    "    if epoch % 10 == 0:\n",
    "        tqdm.write(\"Epoch: {0} Loss: {1} Acc: {2}\".format(epoch, loss_history[-1], acc_history[-1]))\n",
    "\n",
    "# 使用Matplotlib绘制损失和准确率的曲线图\n",
    "import matplotlib.pyplot as plt\n",
    "plt.plot(loss_history, label='loss')\n",
    "plt.plot(acc_history, label='accuracy')\n",
    "plt.legend()\n",
    "plt.show()\n",
    "\n",
    "# 输出准确率\n",
    "print(\"Accuracy:\", acc_history[-1])"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
